{
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  },
  "orig_nbformat": 2,
  "kernelspec": {
   "name": "python388jvsc74a57bd078470a4d8b69fe2d0878f352c66848692b37e6a76f4f59b3a6c7d1a681413e8d",
   "display_name": "Python 3.8.8 64-bit"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2,
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "from PIL import Image \n",
    "import numpy as np\n",
    " \n",
    "\n",
    "\n",
    "pictureDir = \"/home/wyz/Desktop/ai第5次实验/CNN图像分类/CNN/animal/\"\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "'''\n",
    "需求1\n",
    "每个图片的尺寸是不一样的，需要统一\n",
    "\n",
    "方案1\n",
    "需要通过padding进行填充\n",
    "padding的方式有2种\n",
    "1.把小图放到左上角\n",
    "2.把小图放到中间\n",
    "由于图片的位置不影响cnn的学习能力\n",
    "方便起见，就用第1种方法\n",
    "'''\n",
    "\n",
    "\n",
    "\n",
    "'''\n",
    "feat:\n",
    "图片读取函数\n",
    "每种动物代表一个类别\n",
    "\n",
    "\n",
    "\n",
    "param：\n",
    "pictureDir：放图片的根目录的路径\n",
    "\n",
    "return：\n",
    "tagIdx2tagName : 类别号对应的类别名字\n",
    "x :  数据, np.Array\n",
    "y ： 标签， np.Array\n",
    "'''\n",
    "def readPicture(pictureDir:str):\n",
    "    x = []\n",
    "    y = []\n",
    "    tagIdx2tagName = {}\n",
    "    for tag, animalDirName in enumerate(os.listdir(pictureDir)):\n",
    "        animalDir = pictureDir + \"/\" + animalDirName\n",
    "        tagIdx2tagName[tag] = animalDirName\n",
    "        for pictureName in os.listdir(animalDir)[:100]:\n",
    "            picturePath = animalDir + '/' + pictureName\n",
    "            picture = Image.open(picturePath)\n",
    "            pictureArray = np.array(picture)\n",
    "            \n",
    "            x.append(pictureArray)\n",
    "            y.append(tag)\n",
    "    #进行padding\n",
    "    _, x = padPictureArray(x)\n",
    "    # print(type(x))\n",
    "    return tagIdx2tagName, np.array(x), np.array(y)\n",
    "\n",
    "\n",
    "'''\n",
    "param：\n",
    "x： list<Array> 放图片np数组的list\n",
    "\n",
    "\n",
    "return:\n",
    "maxShape: (x,y) 最大的图片的尺寸\n",
    "x：list<Array> 处理完之后的图片list\n",
    "'''\n",
    "def padPictureArray(x):\n",
    "    # 找到里面的最大图片的尺寸\n",
    "    maxX = 0\n",
    "    maxY = 0\n",
    "    for pic in x:\n",
    "        maxX = max(maxX, pic.shape[0])\n",
    "        maxY = max(maxY, pic.shape[1])\n",
    "    \n",
    "    for idx, pic in enumerate(x):\n",
    "        paddedPic = np.pad(pic, ( (0, maxX - pic.shape[0]), (0, maxY - pic.shape[1]), (0, 0) ) )\n",
    "        x[idx] = paddedPic / 255\n",
    "    \n",
    "    return (maxX, maxY), x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "(1200, 3, 300, 300)\n(300, 3, 300, 300)\n300\n300\n<class 'numpy.int64'>\n"
     ]
    }
   ],
   "source": [
    "from torch.utils.data import Dataset\n",
    "class AnimalDataset(Dataset):\n",
    "    def __init__(self, x, y, train=True, transform=None):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "\n",
    "    def __len__(self):\n",
    "        return self.x.shape[0]\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        return self.x[index], int(self.y[index])\n",
    "        # return torch.from_numpy(self.x[index]), torch.from_numpy(self.y[index])\n",
    "\n",
    "refer, xx, yy = readPicture(pictureDir)\n",
    "\n",
    "# 图片维度变换\n",
    "lenX1 = xx.shape[1]\n",
    "lenX2 = xx.shape[2]\n",
    "xx = xx.reshape(xx.shape[0], 3, lenX1, lenX2)\n",
    "\n",
    "\n",
    "# 划分\n",
    "split = int(xx.shape[0]*0.8)\n",
    "train_xx = xx[:split]\n",
    "train_yy = yy[:split]\n",
    "test_xx = xx[split:]\n",
    "test_yy = yy[split:]\n",
    "\n",
    "animalTrainDataSet = AnimalDataset(train_xx,train_yy)\n",
    "animalTestDataSet = AnimalDataset(test_xx,test_yy)\n",
    "\n",
    "\n",
    "print(train_xx.shape)\n",
    "print(test_xx.shape)\n",
    "print(lenX1)\n",
    "print(lenX2)\n",
    "\n",
    "print(type(test_yy[0]))\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "ok\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "from torch.autograd import Variable\n",
    "import torch.utils.data as Data\n",
    "import torchvision #包括了一些数据库，图片的数据库也包含了\n",
    "import torch.nn.functional as F\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "# 模型定义\n",
    "\n",
    "\n",
    "\n",
    "# 模型训练相关参数\n",
    "learning_rate = 1e-4\n",
    "keep_prob_rate = 0.7 #\n",
    "max_epoch = 2\n",
    "BATCH_SIZE = 32\n",
    "\n",
    "picChannel = 3\n",
    "\n",
    "\n",
    "device = \"cpu\"\n",
    "\n",
    "train_loader = DataLoader(dataset = animalTrainDataSet, batch_size = BATCH_SIZE, shuffle = True)\n",
    "test_loader = DataLoader(dataset = animalTestDataSet, batch_size = BATCH_SIZE, shuffle = True)\n",
    "\n",
    "\n",
    "class MyCNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(MyCNN, self).__init__()\n",
    "        self.conv1 = nn.Sequential(\n",
    "            nn.Conv2d(\n",
    "                # patch 7 * 7 ; 1  in channels ; 32 out channels ; ; stride is 1\n",
    "                # padding style is same(that means the convolution opration's input and output have the same size)\n",
    "                in_channels = picChannel,  \n",
    "                out_channels = 16,\n",
    "                kernel_size = 5,\n",
    "                stride = 5,\n",
    "                # padding = 4\n",
    "            ),\n",
    "            nn.ReLU(), \n",
    "            nn.MaxPool2d(kernel_size = 2),  \n",
    "        )\n",
    "        self.conv2 = nn.Sequential( # ???\n",
    "            nn.Conv2d(\n",
    "                # patch 7 * 7 ; 1  in channels ; 32 out channels ; ; stride is 1\n",
    "                # padding style is same(that means the convolution opration's input and output have the same size)\n",
    "                in_channels = 16,  \n",
    "                out_channels = 32,\n",
    "                kernel_size = 5,\n",
    "                stride = 5,\n",
    "                # padding = 4\n",
    "            ),\n",
    "            nn.ReLU(), \n",
    "            nn.MaxPool2d(kernel_size = 2)\n",
    "        )\n",
    "        self.out1 = nn.Linear(3 * 3 * 32, 64)   # full connection layer one\n",
    "        self.dropout = nn.Dropout(keep_prob_rate)\n",
    "        self.out2 = nn.Linear(64, 10, bias=True)\n",
    "\n",
    "\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        x = self.conv2(x)\n",
    "        x = x.view(x.size(0), -1)  # flatten the output of conv2 to (batch_size ,32 * 7 * 7) \n",
    "        # print(x.shape)\n",
    "        out1 = self.out1(x)\n",
    "        out1 = F.relu(out1)\n",
    "        out1 = self.dropout(out1)\n",
    "        out2 = self.out2(out1)\n",
    "        output = F.softmax(out2)\n",
    "        return output\n",
    "\n",
    "\n",
    "\n",
    "def train(cnn):\n",
    "    size = train_xx.shape[0]\n",
    "    optimizer = torch.optim.Adam(cnn.parameters(), lr=learning_rate)\n",
    "    loss_func = nn.CrossEntropyLoss()\n",
    "\n",
    "\n",
    "    for epoch in range(max_epoch):\n",
    "        for step, data in enumerate(train_loader):\n",
    "            x_, y_ = data\n",
    "            \n",
    "            x = x_.float()\n",
    "            y = y_.to(torch.int64)\n",
    "\n",
    "            # print(\"111\")\n",
    "            # print(x.dtype)\n",
    "\n",
    "            output = cnn(x)\n",
    "            \n",
    "            # print(\"222\")\n",
    "            # print(output.dtype)\n",
    "            # print(y.dtype)\n",
    "\n",
    "            loss = loss_func(output, y)\n",
    "\n",
    "            optimizer.zero_grad()\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "            \n",
    "            if step != 0 and step % 10 == 0:\n",
    "                current = step * len(x_)\n",
    "                print(f\"step: {step}, loss: {loss:>10f}, [{current:>5d}/{size:>5d}]\")\n",
    "\n",
    "\n",
    "def test(cnn):\n",
    "    size = test_xx.shape[0]\n",
    "    test_loss\n",
    "    # global prediction\n",
    "    y_pre = cnn(test_xx)\n",
    "    _,pre_index= torch.max(y_pre,1)\n",
    "    pre_index= pre_index.view(-1)\n",
    "    prediction = pre_index.data.numpy()\n",
    "    correct  = np.sum(prediction == test_y)\n",
    "    return correct / 500.0\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "print(\"ok\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "<ipython-input-67-e88f2a06fb11>:73: UserWarning: Implicit dimension choice for softmax has been deprecated. Change the call to include dim=X as an argument.\n",
      "  output = F.softmax(out2)\n",
      "step: 10, loss:   2.302001, [  320/ 1200]\n",
      "step: 20, loss:   2.296751, [  640/ 1200]\n",
      "step: 30, loss:   2.296537, [  960/ 1200]\n",
      "step: 10, loss:   2.281689, [  320/ 1200]\n",
      "step: 20, loss:   2.281781, [  640/ 1200]\n",
      "step: 30, loss:   2.261873, [  960/ 1200]\n",
      "ok\n"
     ]
    }
   ],
   "source": [
    "myCNN = MyCNN()\n",
    "train(myCNN)\n",
    "\n",
    "print(\"ok\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "torch.float32\n"
     ]
    }
   ],
   "source": [
    "loss_func = nn.CrossEntropyLoss()\n",
    "loss_func(torch.tensor([[0.1,0.2,0.7],[0.8,0.1,0.1]]),torch.tensor([0,1]))\n",
    "\n",
    "print(torch.tensor([0.1,1.2]).dtype)"
   ]
  }
 ]
}